﻿using SlimDX.Direct3D11;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.DXGI;
using SlimDX.DirectInput;
using Device = SlimDX.Direct3D11.Device;
using Effect = SlimDX.Direct3D11.Effect;
using Resource = SlimDX.Direct3D11.Resource;
using EffectFlags = SlimDX.D3DCompiler.EffectFlags;
using System.Drawing;
using System.Diagnostics;
using SlimDX.Multimedia;


namespace InsTron
{
    class GameView : IRenderable
    {
        #region members
        private Device device;
        private DeviceContext immediateContext;
        private Effect effect;
        private EffectTechnique technique_Copy;
        private EffectTechnique technique_ProcessRawOutput;
        private EffectTechnique technique_Message;
        private InputLayout inputLayout_PosTex;
        private Buffer topHalfRectangle;
        private Buffer messageRectangle;

        private Texture2D targetTexture;
        private RenderTargetView targetView;
        private DeviceContext deferredContext;

        private PlayerViewCommons playerViewCommons;
        private PlayerView[] playerView;
        private GameState gameState;

        private Stopwatch stopwatch;
        private long totalFrames;
        private int sceneNum;
        #endregion


        public GameView(Device device, Keyboard keyboard)
        {
            Settings.PlayerInitialized = false;
            this.device = device;
            immediateContext = device.ImmediateContext;
            gameState = new GameState();
            playerViewCommons = new PlayerViewCommons(device, gameState);
            playerView = new PlayerView[3];
            if (Settings.QcaaEnabled)
            {
                playerView[0] = new PlayerView(playerViewCommons);
                playerView[1] = new PlayerView(playerViewCommons, 0.5f, 0.5f);
            }
            else if (Settings.EqcaaEnabled)
            {
                playerView[0] = new PlayerView(playerViewCommons, 0.5f, 0);
                playerView[1] = new PlayerView(playerViewCommons, 0, 0.5f);
            }
            else if (Settings.P112Enabled)
            {
                playerView[0] = new PlayerView(playerViewCommons, 0.253f, -0.335f);
                playerView[1] = new PlayerView(playerViewCommons, -0.253f, 0.335f);
            }
            else if (Settings.P113Enabled)
            {
                playerView[0] = new PlayerView(playerViewCommons, -0.399f, 0.397f);
                playerView[1] = new PlayerView(playerViewCommons, 0.373f, 0.049f);
                playerView[2] = new PlayerView(playerViewCommons, -0.005f, -0.443f);
            }
            else if (Settings.P142Enabled)
            {
                playerView[0] = new PlayerView(playerViewCommons, -0.669f, 0.671f);
                playerView[1] = new PlayerView(playerViewCommons, -0.142f, 0.142f);
            }
            else if (Settings.P143Enabled)
            {
                playerView[0] = new PlayerView(playerViewCommons, -0.100f, 0.866f);
                playerView[1] = new PlayerView(playerViewCommons, -0.739f, 0.580f);
                playerView[2] = new PlayerView(playerViewCommons, -0.478f, 0.156f);
            }
            else
            {
                playerView[0] = new PlayerView(playerViewCommons);
            }

            stopwatch = new Stopwatch();
            totalFrames = 0;
            sceneNum = Constants.SCENE;

            InitializeAll();
        }


        public void InitializeAll()
        {
            LoadEffects();
            ConfigureInputLayouts();
            ConfigureInputAssember();
            PrepareSplitScreenRectangles();
            PrepareMessageRectangle();
            LoadTextures();
            
            stopwatch.Start();
        }


        public void DisposeAll()
        {
            effect.Dispose();
            topHalfRectangle.Dispose();
            messageRectangle.Dispose();
            inputLayout_PosTex.Dispose();
            playerViewCommons.DisposeAll();
            playerView[0].DisposeAll();
            if (Settings.QcaaEnabled | Settings.EqcaaEnabled | Settings.P112Enabled | Settings.P113Enabled | Settings.P142Enabled | Settings.P143Enabled)
                playerView[1].DisposeAll();
            if (Settings.P113Enabled | Settings.P143Enabled)
            {
                playerView[2].DisposeAll();
            }
            targetTexture.Dispose();
            targetView.Dispose();
            deferredContext.Dispose();

            stopwatch.Stop();
            System.Console.WriteLine("\nMeasurments for scene {0}:", sceneNum);
            System.Console.WriteLine("Total time elapsed:\t\t\t{0} ms", stopwatch.ElapsedMilliseconds);
            System.Console.WriteLine("Total frames rendered:\t\t{0} frames", totalFrames);
            System.Console.WriteLine("Average frame render time:\t{0} ms", (double)stopwatch.ElapsedMilliseconds / (double)totalFrames);
            System.Console.WriteLine("Average FPS:\t\t\t\t{0} fsp\n", ((double)totalFrames / (double)stopwatch.ElapsedMilliseconds) * 1000.0);
        }


        private void LoadEffects()
        {
            string errors;
            using (var bytecode = ShaderBytecode.CompileFromFile("shaders\\GameViewEffects.fx", "fx_5_0",
                ShaderFlags.None, EffectFlags.None, null, new HlslIncludeHandler(), out errors))
            {
                effect = new Effect(device, bytecode);
            }
            technique_Copy = effect.GetTechniqueByName("SplitScreen");
            if (Settings.SupersamplingFactor > 1)
                technique_ProcessRawOutput = effect.GetTechniqueByName("SplitScreenSS");
            else if (Settings.QcaaEnabled)
                technique_ProcessRawOutput = effect.GetTechniqueByName("SplitScreenQCAA");
            else if (Settings.EqcaaEnabled)
                technique_ProcessRawOutput = effect.GetTechniqueByName("SplitScreenLOL");
            else if (Settings.P112Enabled)
                technique_ProcessRawOutput = effect.GetTechniqueByName("SplitScreenP112");
            else if (Settings.P113Enabled)
                technique_ProcessRawOutput = effect.GetTechniqueByName("SplitScreenP113");
            else if (Settings.P142Enabled)
                technique_ProcessRawOutput = effect.GetTechniqueByName("SplitScreenP142");
            else if (Settings.P143Enabled)
                technique_ProcessRawOutput = effect.GetTechniqueByName("SplitScreenP143");
            else
                technique_ProcessRawOutput = effect.GetTechniqueByName("SplitScreen");
            technique_Message = effect.GetTechniqueByName("Message");
        }


        private void ConfigureInputLayouts()
        {
            ShaderSignature inputSignature;

            inputSignature = technique_Copy.GetPassByIndex(0).Description.Signature;
            inputLayout_PosTex = new InputLayout(device, inputSignature, VertexDeclarations.InputElements_PosTex);
            inputSignature.Dispose();
        }


        private void ConfigureInputAssember()
        {
            immediateContext.InputAssembler.InputLayout = inputLayout_PosTex;
            immediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
        }


        private void PrepareSplitScreenRectangles()
        {
            var rectangleTop = new DataStream(20 * 6, true, true);
            rectangleTop.Write(new Vector3(-1.0f, 1.0f, 0.0f));
            rectangleTop.Write(new Vector2(0.0f, 0.0f));
            rectangleTop.Write(new Vector3(1.0f, 1.0f, 0.0f));
            rectangleTop.Write(new Vector2(1.0f, 0.0f));
            rectangleTop.Write(new Vector3(-1.0f, -1.0f, 0.0f));
            rectangleTop.Write(new Vector2(0.0f, 1.0f));
            rectangleTop.Write(new Vector3(1.0f, 1.0f, 0.0f));
            rectangleTop.Write(new Vector2(1.0f, 0.0f));
            rectangleTop.Write(new Vector3(1.0f, -1.0f, 0.0f));
            rectangleTop.Write(new Vector2(1.0f, 1.0f));
            rectangleTop.Write(new Vector3(-1.0f, -1.0f, 0.0f));
            rectangleTop.Write(new Vector2(0.0f, 1.0f));
            rectangleTop.Position = 0;

            topHalfRectangle = new Buffer(device, rectangleTop, 20 * 6, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
        }


        private void PrepareMessageRectangle()
        {
            var rectangle = new DataStream(20 * 6, true, true);
            rectangle.Write(new Vector3(-0.5f, 0.33f, 0.0f));
            rectangle.Write(new Vector2(0.0f, 0.0f));
            rectangle.Write(new Vector3(0.5f, 0.33f, 0.0f));
            rectangle.Write(new Vector2(1.0f, 0.0f));
            rectangle.Write(new Vector3(-0.5f, -0.33f, 0.0f));
            rectangle.Write(new Vector2(0.0f, 1.0f));
            rectangle.Write(new Vector3(0.5f, 0.33f, 0.0f));
            rectangle.Write(new Vector2(1.0f, 0.0f));
            rectangle.Write(new Vector3(0.5f, -0.33f, 0.0f));
            rectangle.Write(new Vector2(1.0f, 1.0f));
            rectangle.Write(new Vector3(-0.5f, -0.33f, 0.0f));
            rectangle.Write(new Vector2(0.0f, 1.0f));
            rectangle.Position = 0;

            messageRectangle = new Buffer(device, rectangle, 20 * 6, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
        }


        private void LoadTextures()
        {
            Texture2DDescription textDesc = new Texture2DDescription
            {
                Format = Format.R8G8B8A8_UNorm,
                BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                Height = Settings.CurrentResolution.Height,
                Width = Settings.CurrentResolution.Width,
                ArraySize = 1,
                CpuAccessFlags = CpuAccessFlags.None,
                MipLevels = 1,
                OptionFlags = ResourceOptionFlags.None,
                SampleDescription = new SampleDescription(1, 0),
                Usage = ResourceUsage.Default
            };


            targetTexture = new Texture2D(device, textDesc);
            targetView = new RenderTargetView(device, targetTexture);

            deferredContext = new DeviceContext(device);
            deferredContext.InputAssembler.InputLayout = inputLayout_PosTex;
            deferredContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            deferredContext.OutputMerger.SetTargets(targetView);
            deferredContext.Rasterizer.SetViewports(new Viewport(0.0f, 0.0f, Settings.CurrentResolution.Width, Settings.CurrentResolution.Height));          
        }


        public void RenderScene()
        {
            playerView[0].RenderScene(immediateContext);

            if ((Settings.QcaaEnabled | Settings.EqcaaEnabled | Settings.P112Enabled | Settings.P113Enabled | Settings.P142Enabled | Settings.P143Enabled))
            {
                playerView[1].RenderScene(immediateContext);
                //Texture2D.SaveTextureToFile(immediateContext, playerView[1].TargetTexture, ImageFileFormat.Bmp, "C:\\Users\\Mith\\Documents\\Studia\\MGR\\out1.bmp");
                using (var resource = new ShaderResourceView(device, playerView[1].TargetTexture))
                    effect.GetVariableByName("xFinalImageHalf2").AsResource().SetResource(resource);
            }
            if (Settings.P113Enabled | Settings.P143Enabled)
            {
                playerView[2].RenderScene(immediateContext);
                //Texture2D.SaveTextureToFile(immediateContext, playerView[1].TargetTexture, ImageFileFormat.Bmp, "C:\\Users\\Mith\\Documents\\Studia\\MGR\\out2.bmp");
                using (var resource = new ShaderResourceView(device, playerView[2].TargetTexture))
                    effect.GetVariableByName("xFinalImageHalf3").AsResource().SetResource(resource);
            }

            //Texture2D.SaveTextureToFile(immediateContext, playerView[0].TargetTexture, ImageFileFormat.Bmp, "C:\\Users\\Mith\\Documents\\Studia\\MGR\\out0.bmp");
            
            deferredContext.ClearRenderTargetView(targetView, Color.Black);
            deferredContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(topHalfRectangle, 20, 0));
            using (var resource = new ShaderResourceView(device, playerView[0].TargetTexture))
                effect.GetVariableByName("xFinalImageHalf").AsResource().SetResource(resource);
            for (int i = 0; i < technique_ProcessRawOutput.Description.PassCount; i++)
            {
                technique_ProcessRawOutput.GetPassByIndex(i).Apply(deferredContext);
                deferredContext.Draw(6, 0);
            }

            CommandList commandList = deferredContext.FinishCommandList(true);
            immediateContext.ExecuteCommandList(commandList, true);
            commandList.Dispose();

            Texture2D.SaveTextureToFile(immediateContext, targetTexture, ImageFileFormat.Bmp, 
                "C:\\Users\\Mith\\Documents\\Studia\\MGR\\outF_" + Constants.SCENE + "_" + Settings.Method.ToString() + ".bmp");

            immediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(topHalfRectangle, 20, 0));
            using (var resource = new ShaderResourceView(device, targetTexture))
                effect.GetVariableByName("xFinalImageHalf").AsResource().SetResource(resource);
            for (int i = 0; i < technique_Copy.Description.PassCount; i++)
            {
                technique_Copy.GetPassByIndex(i).Apply(immediateContext);
                immediateContext.Draw(6, 0);
            }

            totalFrames++;          
        }
    }
}
